/*
* ====================================================================
* Copyright (c) 2013-2014 Neudesic.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that redistributions of source
* retain the above copyright notice, this list of conditions and the
* following disclaimer.
*
* This source file includes software written by Neudesic, LLC (http://www.neudesic.com).
*
* THIS SOFTWARE IS PROVIDED BY Neudesic, LLC ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY, NON-INFRINGEMENT, AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  FURTHER, IN NO EVENT SHALL
* Neudesic, LLC (THE AUTHOR) OR ANY CONTRIBUTORS BE LIABLE FOR ANY DAMAGES
* WHATSOEVER THAT ARE RELATED TO THE SOFTWARE [INCLUDING BUT NOT LIMITED TO,
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* SUCH AS LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION, HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)] EVEN IF ADVISED OF THE POSSIBILITY
* OF SUCH DAMAGES.
*
* ====================================================================
*/
#ifndef __SOCKETINFORMATION_H__
#define __SOCKETINFORMATION_H__

#include "isocketcontainer.h"
#include <windows.networking.sockets.h>

using ABI::Windows::Foundation::Collections::IVectorView;
using ABI::Windows::Networking::Connectivity::IConnectionProfile;
using ABI::Windows::Networking::Connectivity::INetworkAdapter;
using ABI::Windows::Networking::IHostName;
using ABI::Windows::Networking::IHostNameFactory;
using ABI::Windows::Networking::HostNameType;
using Microsoft::WRL::Wrappers::HStringReference;
using Microsoft::WRL::ComPtr;

namespace Neudesic {
	namespace Sockets {
		struct HostAndPortInformation
		{
			std::wstring host;
			std::wstring port;
			HostNameType type;
		};
		template <typename T, typename TInfo>
		class SocketInformation
		{
		public:
			SocketInformation(ComPtr<T>& socket)
			{
				this->socket = socket;
				this->socket->get_Information(&this->socketInformation);
			}

			HRESULT GetLocalHostAndPortInfo(HostAndPortInformation* info) const
			{
				info->type = HostNameType::HostNameType_Ipv4;
				HRESULT hr = E_FAIL;
				if (this->socketInformation)
				{
					hr = this->SetLocalHostInformation(info);
					if (SUCCEEDED(hr))
						hr = this->SetLocalPortInformation(info);
				}
				return hr;
			}

			//static HRESULT GetConnectionProfile(HostNameType type)
			//{
			//	ComPtr<ABI::Windows::Networking::Connectivity::INetworkInformationStatics> networkStatics;
			//	HRESULT hr = ABI::Windows::Foundation::GetActivationFactory(HStringReference(RuntimeClass_Windows_Networking_Connectivity_NetworkInformation).Get(), &networkStatics);
			//	ComPtr<IConnectionProfile> profile;
			//	hr = networkStatics->GetInternetConnectionProfile(&profile);
			//	ComPtr<IVectorView<HSTRING>> networkNames;
			//	profile->GetNetworkNames(&networkNames);
			//	UINT32 size;
			//	networkNames->get_Size(&size);
			//	std::wstring buffer;
			//	for (UINT32 i = 0; i < size; i++)
			//	{
			//		if (i > 0)
			//			buffer += L", ";
			//		HSTRING networkName;
			//		networkNames->GetAt(i, &networkName);
			//		buffer += ::ConvertToWideString(&networkName);
			//		::WindowsDeleteString(networkName);
			//	}
			//	::ShowInfoMessage(L"Network names found:%s", buffer.c_str());
			//	ComPtr<INetworkAdapter> networkAdapter;
			//	profile->get_NetworkAdapter(&networkAdapter);
			//	UINT32 ianaInterfaceType;
			//	networkAdapter->get_IanaInterfaceType(&ianaInterfaceType);
			//	ComPtr<IVectorView<ABI::Windows::Networking::HostName*>> hostNames;
			//	networkStatics->GetHostNames(&hostNames);
			//	hostNames->get_Size(&size);
			//	buffer.clear();
			//	for (UINT32 i = 0; i < size; i++)
			//	{
			//		if (i > 0)
			//			buffer += L", ";

			//		ComPtr<ABI::Windows::Networking::IHostName> hostName;
			//		hostNames->GetAt(i, &hostName);
			//		ABI::Windows::Networking::HostNameType hostNameType;
			//		hostName->get_Type(&hostNameType);
			//		//if (hostNameType == type)
			//		{
			//			HSTRING canonicalName;
			//			hostName->get_CanonicalName(&canonicalName);
			//			UINT len;
			//			std::wstring wname = std::wstring(::WindowsGetStringRawBuffer(canonicalName, &len));
			//			buffer += wname;
			//			::WindowsDeleteString(canonicalName);
			//		}
			//	}
			//	::ShowInfoMessage(L"CanonicalNames found:%s", buffer.c_str());

			//	//networkStatics->GetConnectionProfiles()
			//	return 0;
			//}

			HRESULT GetRemoteHostAndPortInfo(HostAndPortInformation* info) const
			{
				info->type = HostNameType::HostNameType_Ipv4;
				HRESULT hr = E_FAIL;
				if (this->socketInformation)
				{
					hr = SetRemoteHostInformation(info);
					if (SUCCEEDED(hr))
						hr = this->SetRemotePortInformation(info);
				}
				return hr;
			}

		private:
			HRESULT SetLocalHostInformation(HostAndPortInformation* info) const
			{
				ComPtr<IHostName> hostName;
				HRESULT hr = this->socketInformation->get_LocalAddress(&hostName);
				if (::ContinueIfSucceeded(hr, L"Could not get socket local address for container [%d] socket [%d].", this->socket))
				{
					if (nullptr == hostName)
					{
						//::ShowWarningMessage(L"Could not get local HostName for container [%d] socket [%d]. Likely cause is that the socket is not connected.", this->socket);
					}
					else
					{
						this->SetCanonicalName(hostName, info->host);
						this->SetHostNameType(hostName, info->type);
					}
				}
				return hr;
			}

			HRESULT SetLocalPortInformation(HostAndPortInformation* info) const
			{
				HSTRING hPort;
				HRESULT hr = this->socketInformation->get_LocalPort(&hPort);
				if (::ContinueIfSucceeded(hr, L"Could not get local port for container [%d] socket [%d].", this->socket))
				{
					if (hPort)
					{
						info->port = ::ConvertToWideString(&hPort);
						::WindowsDeleteString(hPort);
					}
				}
				return hr;
			}

			HRESULT SetRemoteHostInformation(HostAndPortInformation* info) const
			{
				ComPtr<IHostName> hostName;
				HRESULT hr = this->socketInformation->get_RemoteAddress(&hostName);
				if (::ContinueIfSucceeded(hr, L"Call to get remote address failed for container [%d] socket [%d].", this->socket))
				{
					if (nullptr == hostName)
					{
						//::ShowWarningMessage(L"Could not get remote HostName for container [%d] socket [%d]. Likely cause is that the socket is not connected.", this->socket);
					}
					else
					{
						this->SetCanonicalName(hostName, info->host);
						this->SetHostNameType(hostName, info->type);
					}
				}

				return hr;
			}

			HRESULT SetRemotePortInformation(HostAndPortInformation* info) const
			{
				HSTRING hPort;
				HRESULT hr = socketInformation->get_RemotePort(&hPort);

				if (::ContinueIfSucceeded(hr, L"Could not get remote port for container [%d] socket [%d].", this->socket))
				{
					if (hPort)
					{
						info->port = ::ConvertToWideString(&hPort);
						::WindowsDeleteString(hPort);
					}
				}
				return hr;
			}

			HRESULT SetCanonicalName(const ComPtr<IHostName>& hostName, std::wstring& nameToSet) const
			{
				HSTRING hname;
				HRESULT hr = hostName->get_CanonicalName(&hname);
				if (::ContinueIfSucceeded(hr, L"Setting name failed on container [%d] socket [%d]", this->socket))
				{
					nameToSet = ::ConvertToWideString(&hname);
					::WindowsDeleteString(hname);
				}
				return hr;
			}

			HRESULT SetHostNameType(const ComPtr<IHostName>& hostName, HostNameType& hostNameType) const
			{
				return hostName->get_Type(&hostNameType);
			}

		private:
			ComPtr<T> socket;
			ComPtr<TInfo> socketInformation;
		};
	}
};

#endif